#include "wx/wxprec.h"
#if wxUSE_POPUPWIN

#include "wx/popupwin.h"

#ifndef WX_PRECOMP
#include "wx/combobox.h"
#include "wx/app.h"
#include "wx/log.h"
#endif

#include "wx/display.h"
#include "wx/recguard.h"

#ifdef __WXUNIVERSAL__
#include "wx/univ/renderer.h"
#include "wx/scrolbar.h"
#endif

#include <gtk/gtk.h>

IMPLEMENT_DYNAMIC_CLASS( wxPopupWindow, wxWindow )
IMPLEMENT_DYNAMIC_CLASS( wxPopupTransientWindow, wxPopupWindow )

#if wxUSE_COMBOBOX && defined(__WXUNIVERSAL__)
IMPLEMENT_DYNAMIC_CLASS( wxPopupComboWindow, wxPopupTransientWindow )
#endif

class wxPopupWindowHandler : public wxEvtHandler {
  public:
    wxPopupWindowHandler( wxPopupTransientWindow *popup ) : m_popup( popup ) {}
  protected:
    void OnLeftDown( wxMouseEvent& event );
  private:
    wxPopupTransientWindow *m_popup;
    DECLARE_EVENT_TABLE()
    DECLARE_NO_COPY_CLASS( wxPopupWindowHandler )
};

class wxPopupFocusHandler : public wxEvtHandler {
  public:
    wxPopupFocusHandler( wxPopupTransientWindow *popup ) : m_popup( popup ) {}
  protected:
    void OnKillFocus( wxFocusEvent& event );
    void OnKeyDown( wxKeyEvent& event );
  private:
    wxPopupTransientWindow *m_popup;
    DECLARE_EVENT_TABLE()
    DECLARE_NO_COPY_CLASS( wxPopupFocusHandler )
};

BEGIN_EVENT_TABLE( wxPopupWindowHandler, wxEvtHandler )
  EVT_LEFT_DOWN( wxPopupWindowHandler::OnLeftDown )
END_EVENT_TABLE()

BEGIN_EVENT_TABLE( wxPopupFocusHandler, wxEvtHandler )
  EVT_KILL_FOCUS( wxPopupFocusHandler::OnKillFocus )
  EVT_KEY_DOWN( wxPopupFocusHandler::OnKeyDown )
END_EVENT_TABLE()

BEGIN_EVENT_TABLE( wxPopupTransientWindow, wxPopupWindow )
#if defined( __WXMSW__ ) || defined( __WXMAC__ )
  EVT_IDLE( wxPopupTransientWindow::OnIdle )
#endif
END_EVENT_TABLE()

wxPopupWindowBase::~wxPopupWindowBase() {
}

bool wxPopupWindowBase::Create( wxWindow* ( parent ), int ( flags ) ) {
  return true;
}

void wxPopupWindowBase::Position( const wxPoint& ptOrigin, const wxSize& size ) {
  wxPoint posScreen;
  wxSize sizeScreen;
  const int displayNum = wxDisplay::GetFromPoint( ptOrigin );
  if( displayNum != wxNOT_FOUND ) {
    const wxRect rectScreen = wxDisplay( displayNum ).GetGeometry();
    posScreen = rectScreen.GetPosition();
    sizeScreen = rectScreen.GetSize();
  } else {
    posScreen = wxPoint( 0, 0 );
    sizeScreen = wxGetDisplaySize();
  }
  const wxSize sizeSelf = GetSize();
  wxCoord y = ptOrigin.y + size.y;
  if( y + sizeSelf.y > posScreen.y + sizeScreen.y ) {
    if( ptOrigin.y > sizeSelf.y ) {
      y -= size.y + sizeSelf.y;
    }
  }
  wxCoord x = ptOrigin.x;
  if( wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft ) {
    x -= size.x;
    x -= sizeSelf.x;
  } else {
    x += size.x;
  }
  if( x + sizeSelf.x > posScreen.x + sizeScreen.x ) {
    if( ptOrigin.x > sizeSelf.x ) {
      x -= size.x + sizeSelf.x;
    }
  }
  Move( x, y, wxSIZE_NO_ADJUSTMENTS );
}

void wxPopupTransientWindow::Init() {
  m_child =
    m_focus = ( wxWindow * )NULL;
  m_handlerFocus = NULL;
  m_handlerPopup = NULL;
}

wxPopupTransientWindow::wxPopupTransientWindow( wxWindow *parent, int style ) {
  Init();
  ( void )Create( parent, style );
}

wxPopupTransientWindow::~wxPopupTransientWindow() {
  if( m_handlerPopup && m_handlerPopup->GetNextHandler() ) {
    PopHandlers();
  }
  wxASSERT( !m_handlerFocus || !m_handlerFocus->GetNextHandler() );
  wxASSERT( !m_handlerPopup || !m_handlerPopup->GetNextHandler() );
  delete m_handlerFocus;
  delete m_handlerPopup;
}

void wxPopupTransientWindow::PopHandlers() {
  if( m_child ) {
    if( !m_child->RemoveEventHandler( m_handlerPopup ) ) {
      m_handlerPopup = NULL;
    }
    if( m_child->HasCapture() ) {
      m_child->ReleaseMouse();
    }
    m_child = NULL;
  }
  if( m_focus ) {
    if( !m_focus->RemoveEventHandler( m_handlerFocus ) ) {
      m_handlerFocus = NULL;
    }
  }
  m_focus = NULL;
}

void wxPopupTransientWindow::Popup( wxWindow *winFocus ) {
  const wxWindowList& children = GetChildren();
  if( children.GetCount() ) {
    m_child = children.GetFirst()->GetData();
  } else {
    m_child = this;
  }
  Show();
  wxASSERT( !m_handlerFocus || !m_handlerFocus->GetNextHandler() );
  wxASSERT( !m_handlerPopup || !m_handlerPopup->GetNextHandler() );
  if( !m_handlerPopup ) {
    m_handlerPopup = new wxPopupWindowHandler( this );
  }
  m_child->PushEventHandler( m_handlerPopup );
  #if defined(__WXMSW__)
  if( ::GetWindowLong( GetHwnd(), GWL_STYLE ) & WS_POPUP )
  #endif
  {
    m_focus = winFocus ? winFocus : this;
    m_focus->SetFocus();
  }
  #if defined( __WXMSW__ ) || defined( __WXMAC__ )
  m_focus = FindFocus();
  #elif defined(__WXGTK__)
  m_focus = this;
  #endif
  if( m_focus ) {
    if( !m_handlerFocus ) {
      m_handlerFocus = new wxPopupFocusHandler( this );
    }
    m_focus->PushEventHandler( m_handlerFocus );
  }
}

bool wxPopupTransientWindow::Show( bool show ) {
  if( !show ) {
    gdk_pointer_ungrab( ( guint32 )GDK_CURRENT_TIME );
    gtk_grab_remove( m_widget );
  }
  bool ret = wxPopupWindow::Show( show );
  if( show ) {
    gtk_grab_add( m_widget );
    gdk_pointer_grab( m_widget->window, TRUE,
                      ( GdkEventMask )
                      ( GDK_BUTTON_PRESS_MASK |
                        GDK_BUTTON_RELEASE_MASK |
                        GDK_POINTER_MOTION_HINT_MASK |
                        GDK_POINTER_MOTION_MASK ),
                      ( GdkWindow * ) NULL,
                      ( GdkCursor * ) NULL,
                      ( guint32 )GDK_CURRENT_TIME );
  }
  return ret;
}

void wxPopupTransientWindow::Dismiss() {
  Hide();
  PopHandlers();
}

void wxPopupTransientWindow::DismissAndNotify() {
  Dismiss();
  OnDismiss();
}

void wxPopupTransientWindow::OnDismiss() {
}

bool wxPopupTransientWindow::ProcessLeftDown( wxMouseEvent& ( event ) ) {
  return false;
}

#if defined( __WXMSW__ ) || defined( __WXMAC__ )
void wxPopupTransientWindow::OnIdle( wxIdleEvent& event ) {
  event.Skip();
  if( IsShown() && m_child ) {
    wxPoint pos = ScreenToClient( wxGetMousePosition() );
    wxRect rect( GetSize() );
    if( rect.Contains( pos ) ) {
      if( m_child->HasCapture() ) {
        m_child->ReleaseMouse();
      }
    } else {
      if( !m_child->HasCapture() ) {
        m_child->CaptureMouse();
      }
    }
  }
}
#endif


#if wxUSE_COMBOBOX && defined(__WXUNIVERSAL__)

BEGIN_EVENT_TABLE( wxPopupComboWindow, wxPopupTransientWindow )
  EVT_KEY_DOWN( wxPopupComboWindow::OnKeyDown )
END_EVENT_TABLE()

wxPopupComboWindow::wxPopupComboWindow( wxComboCtrl *parent )
  : wxPopupTransientWindow( parent ) {
  m_combo = parent;
}

bool wxPopupComboWindow::Create( wxComboCtrl *parent ) {
  m_combo = parent;
  return wxPopupWindow::Create( parent );
}

void wxPopupComboWindow::PositionNearCombo() {
  wxPoint ptOrigin = m_combo->ClientToScreen( wxPoint( 0, 0 ) );
  Position( ptOrigin, wxSize( 0, m_combo->GetSize().y ) );
}

void wxPopupComboWindow::OnDismiss() {
  m_combo->OnPopupDismiss();
}

void wxPopupComboWindow::OnKeyDown( wxKeyEvent& event ) {
  m_combo->ProcessEvent( event );
}

#endif

void wxPopupWindowHandler::OnLeftDown( wxMouseEvent& event ) {
  if( m_popup->ProcessLeftDown( event ) ) {
    return;
  }
  wxPoint pos = event.GetPosition();
  #if defined(__WXUNIVERSAL__) && wxUSE_SCROLLBAR
  wxWindow *sbar = NULL;
  #endif
  wxWindow *win = ( wxWindow * )event.GetEventObject();
  switch( win->HitTest( pos.x, pos.y ) ) {
    case wxHT_WINDOW_OUTSIDE: {
      wxMouseEvent event2( event );
      m_popup->ClientToScreen( &event2.m_x, &event2.m_y );
      m_popup->DismissAndNotify();
      wxWindow *winUnder = wxFindWindowAtPoint( event2.GetPosition() );
      if( winUnder ) {
        winUnder->ScreenToClient( &event2.m_x, &event2.m_y );
        event2.SetEventObject( winUnder );
        wxPostEvent( winUnder, event2 );
      }
    }
    break;
    #if defined(__WXUNIVERSAL__) && wxUSE_SCROLLBAR
    case wxHT_WINDOW_HORZ_SCROLLBAR:
      sbar = win->GetScrollbar( wxHORIZONTAL );
      break;
    case wxHT_WINDOW_VERT_SCROLLBAR:
      sbar = win->GetScrollbar( wxVERTICAL );
      break;
      #endif
    default:
      wxFAIL_MSG( _T( "unexpected HitTest() return value" ) );
    case wxHT_WINDOW_CORNER:
    case wxHT_WINDOW_INSIDE:
      event.Skip();
      break;
  }
  #if defined(__WXUNIVERSAL__) && wxUSE_SCROLLBAR
  if( sbar ) {
    pos = sbar->ScreenToClient( win->ClientToScreen( pos ) );
    wxMouseEvent event2 = event;
    event2.m_x = pos.x;
    event2.m_y = pos.y;
    ( void )sbar->GetEventHandler()->ProcessEvent( event2 );
  }
  #endif
}

void wxPopupFocusHandler::OnKillFocus( wxFocusEvent& event ) {
  wxWindow *win = event.GetWindow();
  while( win ) {
    if( win == m_popup ) {
      return;
    }
    win = win->GetParent();
  }
  m_popup->DismissAndNotify();
}

void wxPopupFocusHandler::OnKeyDown( wxKeyEvent& event ) {
  static int s_inside;
  wxRecursionGuard guard( s_inside );
  if( guard.IsInside() ) {
    event.Skip();
    return;
  }
  if( !m_popup->GetEventHandler()->ProcessEvent( event ) ) {
    m_popup->DismissAndNotify();
  }
}

#endif
